using System;
using System.Collections;
using Refly.CodeDom;
using Refly.CodeDom.Statements;
using Refly.CodeDom.Collections;
using Refly.Templates;
using Refly.CodeDom.Expressions;
using System.Text;
using System.Text.RegularExpressions;
using Doubler.Common;
using Reflector.Framework;
using Doubler.Data;
using Doubler.Helpers;
using Doubler.Helpers.Graph;

namespace Doubler.Templates.Recorder
{

    public class RecorderTemplate : SingleTemplateBase
    {
        
#region Fields

        private RecorderGenerator _Generator;
        private Hashtable _Recorders;
        private IClassDeclaration _DoubleClass;
        private MemberList _MembersProcessed;
        private Boolean _OverrideOverrideables;

#endregion
        
#region Properties

        public RecorderGenerator Generator
        {
            get
            {
                if (_Generator == null)
                    _Generator = new RecorderGenerator();
                return _Generator;
            }
            set
            {
                _Generator = value;
            }
        }

        public Boolean OverrideOverrideables
        {
            get
            {
                return _OverrideOverrideables;
            }
            set
            {
                _OverrideOverrideables = value;
            }
        }

        private MemberList MembersProcessed
        {
            get
            {
                if (_MembersProcessed == null)
                    _MembersProcessed = new MemberList();
                return _MembersProcessed;
            }
            set
            {
                _MembersProcessed = value;
            }
        }

        private IClassDeclaration DoubleClass
        {
            get
            {
                return _DoubleClass;
            }
            set
            {
                _DoubleClass = value;
            }
        }

        private Hashtable Recorders
        {
            get
            {
                if (_Recorders == null)
                    _Recorders = new Hashtable();
                return _Recorders;
            }
            set
            {
                _Recorders = value;
            }
        }

        public override string FileName
        {
            get
            {
                return string.Format(base.NameFormat, this.Subject.Name);
            }
        }

        public override string OutputFilePath
        {
            get
            {
                string NamespacePath = Regex.Replace(this.Namespace, @"\.", "/");
                return string.Format("{0}/{1}/{2}.{3}", this.OutputPath, NamespacePath, this.FileName, this.OutputLanguage);
            }
        }

#endregion
        
#region Constructor

        public RecorderTemplate()
            : base("", "Recorder{0}")
        {
            
        }

        public RecorderTemplate(CodeLanguage language, string outDirectoryPath, Reflector.CodeModel.ITypeDeclaration subject, ReflectorServices reflectorServices)
            : base(language, outDirectoryPath, subject, "Recorder{0}", reflectorServices)
        {
            
        }

#endregion
        
#region ControlFlow

        public override void Generate()
        {
            this.MembersProcessed = null;
            this.Recorders = null;
            this.Prepare();
            this.GenerateDouble();
            this.GenerateRecorders();
            this.GenerateRecorderContainer();
            this.Compile();
        }

        private void GenerateDouble()
        {
            this.DoubleClass = base.NamespaceDeclaration.AddClass(FileName);
            if (this.Subject.Interface)
            {
                this.DoubleClass.Interfaces.Add(new StringTypeDeclaration(MetaHelper.GetFullTypeName(this.Subject)));
                this.GenerateDoubleFromInterfaces(this.Subject);
            }
            else
            {
                this.DoubleClass.Parent = new StringTypeDeclaration(MetaHelper.GetFullTypeName(this.Subject));
            }
            this.DoubleClass.Imports.Add(typeof(DoubleAttribute).Namespace);
            this.DoubleClass.Imports.Add("System.ComponentModel");
            
            this.DoubleClass.CustomAttributes.Add(typeof(DoubleAttribute));
            this.DoubleClass.CustomAttributes.Add(typeof(RecorderAttribute));
            this.GenerateDoubleConstructors(this.Subject);
            this.GenerateDoubleProperties(this.Subject);
            this.GenerateDoubleMethods(this.Subject);
            this.GenerateDoubleEvents(this.Subject);
        }

        private void GenerateDoubleFromInterfaces(Reflector.CodeModel.ITypeDeclaration subject)
        {
            foreach (Reflector.CodeModel.ITypeReference Interface in subject.Interfaces)
            {
                this.GenerateDoubleProperties(Interface);
                this.GenerateDoubleMethods(Interface);
                this.GenerateDoubleEvents(Interface);
                this.GenerateDoubleFromInterfaces(Interface.Resolve());
            }
        }

        private void GenerateRecorders()
        {
            foreach(RecorderInfo Recorder in this.Recorders.Values)
            {
                Recorder.Class = this.DoubleClass.AddClass(RecorderGenerator.GetRecorderName(Recorder.Name));

                AttributeDeclaration BlockIntellisense = Recorder.Class.CustomAttributes.Add("EditorBrowsable", new Expression[1] { Expr.Snippet("EditorBrowsableState.Never") });

                GenerationHelper.GeneratePropertyAndField(Recorder.Class, RecorderGenerator.RecordingCalled, this.Services.Search.FindType(typeof(bool).FullName), true, true, System.CodeDom.MemberAttributes.Public);
                GenerationHelper.GeneratePropertyAndField(Recorder.Class, RecorderGenerator.RecordingExceptionToThrow, this.Services.Search.FindType(typeof(System.Exception).FullName), true, true, System.CodeDom.MemberAttributes.Public);
                foreach(ParameterDeclaration Parameter in Recorder.Parameters)
                {
                    string PassedRecordingName = RecorderGenerator.GetPassedRecordingName(Parameter);
                    GenerationHelper.GeneratePropertyAndField(Recorder.Class, PassedRecordingName, Parameter.Type, true, true, System.CodeDom.MemberAttributes.Public);
                }
                if (Recorder.Member is Reflector.CodeModel.IMethodDeclaration && (((Reflector.CodeModel.IMethodDeclaration)Recorder.Member).ReturnType.Type.ToString() != "Void" && ((Reflector.CodeModel.IMethodDeclaration)Recorder.Member).ReturnType.Type.ToString() != "System.Void"))
                {
                    Reflector.CodeModel.IMethodDeclaration Method = (Reflector.CodeModel.IMethodDeclaration)Recorder.Member;
                    StringTypeDeclaration ReturnType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(Method.ReturnType.Type));
                    GenerationHelper.GeneratePropertyAndField(Recorder.Class, RecorderGenerator.RecordingReturnValue, ReturnType, true, true, System.CodeDom.MemberAttributes.Public);
                }
            }
        }

        private void GenerateRecorderContainer()
        {
            IClassDeclaration RecorderClass = this.DoubleClass.AddClass(RecorderGenerator.GetRecordingsClassTypeName(this.Subject.Name));
            PropertyFieldPair Pair = GenerationHelper.GeneratePropertyAndField(this.DoubleClass, RecorderGenerator.RecordingsPropertyName, RecorderClass, true, true, System.CodeDom.MemberAttributes.Public);
            Pair.Property.Get.Insert(0, Stm.IfNull(Expr.This.Field(Pair.Field), Stm.Assign(Expr.This.Field(Pair.Field), Expr.New(RecorderClass.Name))));
            foreach (RecorderInfo Recorder in this.Recorders.Values)
            {
                String RecordingName = Recorder.Name;
                Pair = GenerationHelper.GeneratePropertyAndField(RecorderClass, RecordingName, Recorder.Class, true, true, System.CodeDom.MemberAttributes.Public);
                Pair.Property.Get.Insert(0, Stm.IfNull(Expr.This.Field(Pair.Field), Stm.Assign(Expr.This.Field(Pair.Field), Expr.New(Recorder.Class.Name))));
            }
        }

#endregion
        
#region Constructor Generation

        private void GenerateDoubleConstructors(Reflector.CodeModel.ITypeReference subject)
        {
            foreach (Reflector.CodeModel.IMethodDeclaration Constructor in subject.Resolve().Methods)
            {
                if (Constructor.Name.EndsWith(".ctor"))
                    this.GenerateDoubleConstructor(subject, Constructor);
            }
            if (subject.Resolve().Interface)
            {
                foreach (Reflector.CodeModel.IMethodDeclaration Constructor in this.Services.Search.FindType("System.Object").Methods)
                {
                    if (Constructor.Name.EndsWith(".ctor"))
                        this.GenerateDoubleConstructor(subject, Constructor);
                }
            }
        }

        private void GenerateDoubleConstructor(Reflector.CodeModel.ITypeReference subject, Reflector.CodeModel.IMethodDeclaration constructorInfo)
        {
            IConstructorDeclaration Constructor = this.DoubleClass.AddConstructor();

            foreach (Reflector.CodeModel.IParameterDeclaration ParameterInformation in constructorInfo.Parameters)
            {
                ParameterDeclaration Parameter;
                Refly.CodeDom.StringTypeDeclaration ParameterType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(ParameterInformation.ParameterType));
                Parameter = Constructor.Signature.Parameters.Add(ParameterType, ParameterInformation.Name);
                Constructor.BaseContructorArgs.Add(Expr.Arg(Parameter));
            }

            String RecordingName = RecorderGenerator.GetRecordingName(constructorInfo, "New");

            RecorderInfo Recorder = new RecorderInfo(RecordingName, constructorInfo, Constructor.Signature.Parameters);
            this.Recorders.Add(RecordingName, Recorder);

            Expression MasterRecorder = Expr.This.Prop(RecorderGenerator.RecordingsPropertyName);

            Expression Recording = MasterRecorder.Prop(RecordingName);

            Expression RecordCalled = Recording.Prop(RecorderGenerator.RecordingCalled);
            Statement AssignRecordCalled = Stm.Assign(RecordCalled, Expr.True);
            Constructor.Body.Add(AssignRecordCalled);

            foreach (ParameterDeclaration Parameter in Constructor.Signature.Parameters)
            {
                string PassedRecordingName = RecorderGenerator.GetPassedRecordingName(Parameter);
                Expression RecordPassed = Recording.Prop(PassedRecordingName);
                Statement Assignment = Stm.Assign(RecordPassed, Expr.Arg(Parameter));
                Constructor.Body.Add(Assignment);
            }

            Expression ExceptionToThrow = Recording.Prop(RecorderGenerator.RecordingExceptionToThrow);
            ConditionStatement IfThrow = Stm.IfNotNull(ExceptionToThrow, Stm.Throw(ExceptionToThrow));
            Constructor.Body.Add(IfThrow);
        }

#endregion
        
#region Method Generation

        private void GenerateDoubleMethods(Reflector.CodeModel.ITypeReference subject)
        {
            foreach (Reflector.CodeModel.IMethodDeclaration MethodInformation in subject.Resolve().Methods)
            {
                if (!this.MembersProcessed.Contains(MethodInformation))
                {
                    this.MembersProcessed.Add(MethodInformation);
                    if (((MethodInformation.Abstract) || (this.OverrideOverrideables && MethodInformation.Virtual))
                            && MetaHelper.IsNotNestedMember(MethodInformation)
                            && !MethodInformation.Name.EndsWith(".ctor"))
                    {
                        this.Generator.GenerateRecorderMethod(this.DoubleClass, subject, MethodInformation, this.Recorders);
                    }
                }
            }
            if (subject.Name != "System.Object" && subject.Name != "Object" && subject.Resolve().BaseType != null)
            {
                this.GenerateDoubleMethods(subject.Resolve().BaseType);
            }
        }

        

#endregion
        
#region Event Generation

        public void GenerateDoubleEvents(Reflector.CodeModel.ITypeReference subject)
        {
            foreach (Reflector.CodeModel.IEventDeclaration EventInformation in subject.Resolve().Events)
            {
                if (this.Subject.Interface || this.IsDeclaredOnAnInterface(EventInformation))
                this.GenerateDoubleEvent(subject, EventInformation);
            }
        }

        public bool IsDeclaredOnAnInterface(Reflector.CodeModel.IEventDeclaration eventInformation)
        {
            if (eventInformation.DeclaringType is Reflector.CodeModel.ITypeReference)
            {
                return ((Reflector.CodeModel.ITypeReference)eventInformation.DeclaringType).Resolve().Interface;
            }
            return false;
        }

        public void GenerateDoubleEvent(Reflector.CodeModel.ITypeReference subject, Reflector.CodeModel.IEventDeclaration eventInformation)
        {
            string FullName = String.Empty;
            FullName = MetaHelper.GetFullTypeName(eventInformation.EventType);
            
            EventDeclaration Event = this.DoubleClass.AddEvent(new StringTypeDeclaration(FullName), eventInformation.Name);

            Event.Attributes = (System.CodeDom.MemberAttributes)System.Enum.Parse(typeof(System.CodeDom.MemberAttributes), Reflector.Helper.GetVisibility(eventInformation).ToString());


            string FireMethodName = string.Format("Fire{0}", eventInformation.Name);
            MethodDeclaration FireMethod = this.DoubleClass.AddMethod(FireMethodName);
            foreach (Reflector.CodeModel.IParameterDeclaration ParameterInformation in MetaHelper.GetInvokeMethod(eventInformation).Parameters)
            {
                Refly.CodeDom.StringTypeDeclaration ParameterType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(ParameterInformation.ParameterType));
                ParameterDeclaration Param = FireMethod.Signature.Parameters.Add(ParameterType, ParameterInformation.Name);
                if (ParameterInformation.ParameterType.ToString().Contains("&"))
                    Param.Direction = System.CodeDom.FieldDirection.Ref;
            }
            DelegateInvokeExpression InvokedDelegate = Expr.DelegateInvoke(Expr.This.Event(Event), FireMethod.Signature.ArgumentReferences);
            FireMethod.Body.Add(InvokedDelegate);
        }

#endregion

#region Property Generation

        private void GenerateDoubleProperties(Reflector.CodeModel.ITypeReference subject)
        {
            string PropertyName;
            Reflector.CodeModel.ITypeDeclaration PropertyType;
            bool Read;
            bool Write;
            Reflector.CodeModel.MethodVisibility Visability;
            System.CodeDom.MemberAttributes VisabilityAttribute = System.CodeDom.MemberAttributes.Public;
            foreach (Reflector.CodeModel.IPropertyDeclaration PropertyInformation in subject.Resolve().Properties)
            {
                if (this.MembersProcessed.Contains(PropertyInformation))
                    continue;
                this.MembersProcessed.Add(PropertyInformation);
                Read = false;
                Write = false;

                Visability = Reflector.CodeModel.MethodVisibility.Public;

                GenerationHelper.GetSetterInfo(ref Write, ref Visability, PropertyInformation);

                GenerationHelper.GetGetterInfo(ref Read, ref Visability, PropertyInformation);

                if (Read || Write)
                {
                    PropertyName = PropertyInformation.Name;
                    if (PropertyInformation.PropertyType is Reflector.CodeModel.ITypeReference)
                    {
                        PropertyType = ((Reflector.CodeModel.ITypeReference)PropertyInformation.PropertyType).Resolve();
                    }
                    else
                    {
                        PropertyType = new Reflector.CodeModel.Memory.TypeDeclaration();
                        PropertyType.Name = ((Reflector.CodeModel.IType)PropertyInformation.PropertyType).ToString();
                    }

                    VisabilityAttribute = (System.CodeDom.MemberAttributes)System.Enum.Parse(typeof(System.CodeDom.MemberAttributes), Visability.ToString());

                    if (!subject.Resolve().Interface)
                    {
                        VisabilityAttribute = VisabilityAttribute | System.CodeDom.MemberAttributes.Override;
                    }

                    PropertyFieldPair Pair;
                    if (PropertyName == "Item")
                    {
                        Reflector.CodeModel.ITypeDeclaration IndexType;
                        if (Read)
                        {
                            IndexType = ((Reflector.CodeModel.ITypeReference)PropertyInformation.GetMethod.Parameters[0].ParameterType).Resolve();
                        }
                        else
                        {
                            IndexType = ((Reflector.CodeModel.ITypeReference)PropertyInformation.GetMethod.Parameters[0].ParameterType).Resolve();
                        }
                        Pair = GenerationHelper.GeneratePropertyIndexerAndField(this.DoubleClass, PropertyName, PropertyType, IndexType, Read, Write, VisabilityAttribute);
                    }
                    else
                    {
                        Pair = GenerationHelper.GeneratePropertyAndField(this.DoubleClass, PropertyName, PropertyType, Read, Write, VisabilityAttribute);

                    }

                    if (subject.Resolve().Interface)
                    {
                        Pair.Property.ImplementationTypes.Add(new StringTypeDeclaration(MetaHelper.GetFullTypeName(subject)));
                    }

                    VisabilityAttribute = System.CodeDom.MemberAttributes.Public;

                    if (!Write)
                    {
                        PropertyName = String.Format("Set{0}", PropertyInformation.Name);

                        GenerationHelper.GeneratePropertyAgainstField(this.DoubleClass, PropertyName, PropertyType, Read, true, VisabilityAttribute, Pair.Field);
                    }
                    if (!Read)
                    {
                        PropertyName = String.Format("Get{0}", PropertyInformation.Name);

                        GenerationHelper.GeneratePropertyAgainstField(this.DoubleClass, PropertyName, PropertyType, true, Write, VisabilityAttribute, Pair.Field);
                    }
                }
            }
        }

#endregion


    }
}
